<?php
/**
 * Application
 *
 * Framework Application
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Juan Carlos Orrego $
 * @modified	$Date: 2009-10-21 $
 */

namespace dayscript;

/**
 * Class loader definition
 */
require_once( 'loader/LoaderManager.class.php' );

/**
 * Singleton
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\loader\LoaderManager, \dayscript\ErrorManager, \dayscript\Enviroment, \dayscript\config\ApplicationDefaults,
 * 				\dayscript\log\LogManager, \dayscript\session\Session, \dayscript\datasource\DataSourceManager
 * @since		1.0
 * @modifiedby	$LastChangedBy: Juan Carlos Orrego $
 * @modified	$Date: 2009-10-21 $
 */
class Application	{
	/**
	 * Application Event dispatcher
	 * @var \dayscript\event\EventDispatcher $eventDispatcher
	 */
	private static $eventDispatcher = null;
	/**
	 * Singleton instance
	 * @var \dayscript\Application $instance
	 */
	private static $instance = null;
	/**
	 * Application modules list
	 * @var array $modules
	 */
	private static $modules = array( );
	/**
	 * Application start State
	 * @var boolean $started
	 */
	private static $started = false;
	/**
	 * Application constructor
	 * @see	\dayscript\config\ApplicationDefaults, \dayscript\Map
	 */
	private function __construct( )	{
		/**
		 * Class Autoloders: Without this the rest does not work.
		 */
		\dayscript\loader\LoaderManager::init( );
	}
	
	/**
	 * Returns global Application Instance
	 * @param mixed $configFile
	 * @return \dayscript\Application
	 */
	public static function getInstance( $configFile = null )	{
		if( self::$instance === null )	{
			self::$instance = new self( );
			self::$instance->createDispatchers( );
			self::$instance->init( $configFile );
		}
		return self::$instance;
	}
	
	public static function resetApplication ( $configFile ){
		foreach (self::$modules as $name => $val) {
			self::$modules[$name] = null;
		}
		
		\dayscript\session\Session::shutdownHandler( null, false );
		\dayscript\ErrorManager::shutdownHandler( null, false );
		\dayscript\datasource\DataSourceManager::shutdownHandler( null, false );
		\dayscript\log\LogManager::shutdownHandler( null, false );
		self::$instance->createDispatchers( );
		\dayscript\Enviroment::getInstance( )->reloadConfig( $configFile, new \dayscript\config\ApplicationDefaults( ) );
		\dayscript\log\LogManager::init( );
		\dayscript\datasource\DataSourceManager::init( );
		
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::LOAD ) );
		self::$instance->initModules( );
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::COMPLETE ) );
		self::$started = false;
		self::$instance->start( );
		
	}
	
	
	private function init( $configFile )	{
		
		/**
		 * Avoid WARNING when no date.timezone set before the system starts
		 */
		if( !\ini_get( 'date.timezone' ) )
			\ini_set( 'date.timezone', 'America/Bogota' );
		
		/**
		 * IMPORTANT: Order is really important.
		 */
		
		/**
		 * As the log configuration has not be initilized yet this will store log messajes until that.
		 */
		\dayscript\log\LogManager::log( 'APPLICATION INIT.', \dayscript\log\LogManager::LOG_USER_INFO );
		/**
		 * Class for Error handlers.
		 * This will call its own shutdown and register some error managers. (1)
		 */
		\dayscript\ErrorManager::createGlobalHandlers( );
		/**
		 * Class for Enviroment variables.
		 * This will call its own shutdown for restoring global data but PHP values. (2)
		 */
		\dayscript\Enviroment::getInstance( )->init( $configFile, new \dayscript\config\ApplicationDefaults( ) );
		/**
		 * Class Log for storing information.
		 */
		\dayscript\log\LogManager::init( );
		/**
		 * Class Session for session storing information.
		 */
		\dayscript\session\Session::init( );
		/**
		 * Class DataSourceManager.
		 */
		\dayscript\datasource\DataSourceManager::init( );
		
		
		/**
		 * Application shoutdown registration.
		 */
		\register_shutdown_function( array( __CLASS__, 'shutdownHandler' ) );
		/**
		 * Shutdown register for Session
		 */
		\dayscript\session\Session::registerShutdown( );
		/**
		 * Shutdown register for Error handlers
		 */
		\dayscript\ErrorManager::registerShutdown( );
		/**
		 * Shutdown register for DataSourceManager
		 */
		\dayscript\datasource\DataSourceManager::registerShutdown( );
		/**
		 * Shutdown register for Class Log
		 */
		\dayscript\Enviroment::getInstance( )->registerShutdown( );
		/**
		 * Shutdown register for Enviroment
		 */
		\dayscript\log\LogManager::registerShutdown( );
		
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::LOAD ) );
		
		$this->initModules( );
		
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::COMPLETE ) );
	}
	
	private function initModules( )	{
		$modules = \dayscript\Enviroment::config( 'MODULES', new \dayscript\Map( ) );
		foreach( $modules as $name => $module )	{
			$moduleDirName = ( !empty( $module['moduleName'] ) ) ? $module['moduleName'] : $name;
			if( $module['active'] === true )	{
				$class = (string)$module['class'];
				$file = (string)$module['file'];
				$events = (string)$module['event'];
				
				if ( !$class || ( !\class_exists( $class, false ) ) )	{
					
					if( !$file )
						$file = str_replace ( array( '\\', DIRECTORY_SEPARATOR .DIRECTORY_SEPARATOR ), array( DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ), implode( DIRECTORY_SEPARATOR, array( \dayscript\Enviroment::dir( ), 'modules', $moduleDirName, 'classes', $module['class'] . '.class.php' ) ) );
					
					$file = ( \file_exists( $file ) ? $file : ( \file_exists( \dayscript\Enviroment::dir( ) . $file ) ? \dayscript\Enviroment::dir( ) . $file : null ) );
					
					if( $file )	{
						if( !$events )
							require( $file );
						else
							$this->addEventListener( $file, $events, array( self::$instance, 'filesEventDispatcher' ) );
					}
					else
						throw new \Exception( 'Module "' . $moduleDirName . '" with class "' . $class . '" could not be loaded using file "' . $file . '".', \E_USER_ERROR );
				}
				
				if ( $class )	{
					if( \class_exists( $class, false ) )	{
						$moduleObject = new $class( $name, $module );
						$this->addModule( $moduleObject );
					}
					else
						throw new \Exception( 'Module class "' . $class . '" could not be found using file "' . $file . '".', \E_USER_ERROR );
				}
			}
		}
	}
	
	
	private function createDispatchers( )	{
		$eventTypes = array (
			\dayscript\event\Event::INIT,
			\dayscript\event\Event::LOAD,
			  
			\dayscript\event\Event::COMPLETE,
			
			\dayscript\event\Event::START,
			\dayscript\event\Event::END,
			
			\dayscript\event\Event::RENDER_START,
			\dayscript\event\Event::RENDER,
			\dayscript\event\Event::RENDER_END,
			
			\dayscript\event\Event::MODULE_LOAD,
			\dayscript\event\Event::MODULE_START,
			
			\dayscript\event\Event::MODULE_RENDER_START,
			\dayscript\event\Event::MODULE_RENDER,
			\dayscript\event\Event::MODULE_RENDER_END,
			
			\dayscript\event\Event::MODULE_END,
			\dayscript\event\Event::MODULE_UNLOAD,
			
			\dayscript\event\Event::USER_IDLE,
			\dayscript\event\Event::USER_PRESENT,
			
			\dayscript\event\Event::UNLOAD,
			\dayscript\event\Event::SHUTDOWN
			
		);
		self::$eventDispatcher = new \dayscript\event\EventDispatcher( self::$instance, $eventTypes );
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::INIT ) );
	}
	
	public function modulesEventDispatcher( \dayscript\event\Event $event )	{
		\dayscript\log\LogManager::log( 'APPLICATION MODULE EVENT: "' . $event->getType( ) . '" on "' . $event->getTarget( )->getName( ) . '".', \dayscript\log\LogManager::LOG_USER_INFO );
		if( self::$eventDispatcher->hasEventListener( $event->getType( ) ) )
			self::$eventDispatcher->dispatchEvent( $event );
		
	}
	public function filesEventDispatcher( \dayscript\event\Event $event )	{
		\dayscript\log\LogManager::log( 'APPLICATION FILE EVENT: "' . $event->getType( ) . '" on "' . \get_class( $event->getTarget( ) ) . '".', \dayscript\log\LogManager::LOG_USER_INFO );
		require( Enviroment::dir( ) . $event->getCurrentTarget( ) );
	}
	/**
	 * Add a named module.
	 * This does nothing and returns false if the loader is already registered.
	 * @param \dayscript\application\Module $module
	 */
	public static function addModule( \dayscript\application\Module $module )	{
		if( isset( self::$modules[$module->getName( )] ) )
			return false;
		
		if( $module->init( ) === true && $module->dependencyCheck( ) )	{
			$module->addEventListener(
										self::$instance,
										array(	\dayscript\event\Event::MODULE_LOAD,
												\dayscript\event\Event::MODULE_START,
												\dayscript\event\Event::MODULE_END,
												\dayscript\event\Event::MODULE_UNLOAD,
												\dayscript\event\Event::MODULE_RENDER_START,
												\dayscript\event\Event::MODULE_RENDER,
												\dayscript\event\Event::MODULE_RENDER_END
										),
										array( self::$instance, 'modulesEventDispatcher' )
									);
			$module->createListeners( );
			
			self::$modules[$module->getName( )] = $module;
			\dayscript\log\LogManager::log( 'APPLICATION MODULE ADDED: "' . $module->getName( ) . '"', \dayscript\log\LogManager::LOG_USER_INFO );
			$module->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::MODULE_LOAD ) );
			
			return true;
		}
		return false;
	}
	/**
	 * Method to find a named module.
	 * @param string $name
	 * @return \dayscript\application\Module|null
	 */
	public static function getModule( $name )	{
		if ( isset (self::$modules[$name]) )
			return self::$modules[$name];
		else
			return null;
	}
	/**
	 * Get an array of known loader names.
	 * @return array $keys
	 */
	public static function getModuleNames( )	{
		return \array_keys( self::$modules );
	}
	
	public static function start( )	{
		if( !self::$started )	{
			self::$started = true;
			
			$event = new \dayscript\event\Event( \dayscript\event\Event::START, false, true );
			self::$eventDispatcher->dispatchEvent( $event );
			if( !$event->isStopped( ) )	{
				$event = new \dayscript\event\Event( \dayscript\event\Event::RENDER_START, false, true );
				self::$eventDispatcher->dispatchEvent( $event );
				if( !$event->isStopped( ) )	{
					self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::RENDER, false, true ) );
					self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::RENDER_END, false, true ) );
				}
			}
		}
	}
	
	public function __destruct( )	{
		\dayscript\log\LogManager::log( 'APPLICATION UNLOAD.', \dayscript\log\LogManager::LOG_USER_INFO );
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::UNLOAD ) );
		\dayscript\log\LogManager::log( 'APPLICATION END.', \dayscript\log\LogManager::LOG_USER_INFO );
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::END ) );
		\dayscript\log\LogManager::log( 'APPLICATION BENCHMARK: ' .  "\n" . \trim( \dayscript\util\Benchmark::getInstance( ) ), \dayscript\log\LogManager::LOG_USER_INFO );
		\flush( );
	}
	/**
	 * Global script shutdown Handler
	 * @param mixed $params
	 */
	public static function shutdownHandler( $params = null )	{
		self::$eventDispatcher->dispatchEvent( new \dayscript\event\Event( \dayscript\event\Event::SHUTDOWN ) );
		\dayscript\log\LogManager::log( 'SHUTDOWN APPLICATION (6).', \dayscript\log\LogManager::LOG_USER_INFO );
	}
	/**
	 * @see \dayscript\event\Dispatcher
	 */
	public static function addEventListener( $object, $type, $listener, $useCapture = false, $priority = 0 )	{
		self::$eventDispatcher->addEventListener( $object, $type, $listener, $useCapture, $priority );
	}
	public static function hasEventListener( $type )	{
		self::$eventDispatcher->hasEventListener( $type );
	}
	public static function removeEventListener( $type, $listener, $useCapture )	{
		self::$eventDispatcher->removeEventListener( $type, $listener, $useCapture );
	}
	public static function willTrigger( $type )	{
		self::$eventDispatcher->willTrigger( $type );
	}
	
}